
#include "HashTable.h"

HashTable::HashTable(string nombre) {
	this->nombre = nombre;
	string arch(nombre+"."+ARCH_TABLA_EXT);
	//Abro el archivo de indice Hash.

	if(!archivoHash.init(arch, ABRIR, sizeof(int)))
		archivoHash.init(arch, CREAR, sizeof(int));

	cantidadElementosBuffer = SIZE_BUFFER / sizeof(int);

	armarBuffer(0);
}

HashTable::~HashTable()
{
	escribirBuffer(false);
}

void HashTable::inicializarBuffer()
{
	for(int i=0;i<cantidadElementosBuffer;i++)
	{
		bufferBloques.push_back(-1);
	}
}

void HashTable::armarBuffer(int posInicial)
{
	char* bufferTemp;
	int valor=0;
	int start=0;

	inicializarBuffer();

	posInicial = posInicial / cantidadElementosBuffer;

	//Posicion inicial de buffer en posicion de tabla.
	posInicioBuffer = posInicial * cantidadElementosBuffer;

	//Posicion inicial en bytes.
	posInicial = posInicial * SIZE_BUFFER;

	int sizeArchivo = archivoHash.getCantBloques() * sizeof(int);

	int sizeBuffer = (sizeArchivo > SIZE_BUFFER)?SIZE_BUFFER:sizeArchivo;

	if(sizeBuffer != 0)
	{
		bufferTemp = archivoHash.readDatos(posInicial,sizeBuffer);

		for(int i=0;i<(int)(sizeBuffer/sizeof(int));i++)
		{
			memcpy(&valor,bufferTemp+start,sizeof(int));
			bufferBloques[i]=valor;
			start += sizeof(int);
		}

		free(bufferTemp);
	}
}

void HashTable::escribirBuffer(bool append)
{
	escribirBuffer(append, &archivoHash);
}

void HashTable::escribirBuffer(bool append, BlockedFile* archivo)
{
	int sizeBuffer = getTamanioTablaBuffer();
	char* bufferSerial = new char[sizeBuffer*sizeof(int)];
	int start = 0;

	for(int i=0;i<sizeBuffer;i++)
	{
		memcpy(bufferSerial+start,&bufferBloques[i],sizeof(int));
		start += sizeof(int);
	}

	//Si esta en true escribe al final del archivo.
	if(append)
		archivo->writeDatos(archivo->getCantBloques()*sizeof(int),sizeBuffer*sizeof(int),bufferSerial);
	else
		archivo->writeDatos(posInicioBuffer*sizeof(int),sizeBuffer*sizeof(int),bufferSerial);

	delete[] bufferSerial;
}

int HashTable::getTamanioTablaBuffer()
{
	int cantidad = 0;
	for(int i=0;i<cantidadElementosBuffer;i++)
	{
		if(bufferBloques[i] == -1)
			break;

		cantidad++;
	}
	return cantidad;
}

int HashTable::getTamanioTabla()
{
	//Cuento las posiciones en buffer por que todavia no fueron bajadas a disco
	if(archivoHash.getCantBloques()==0 || archivoHash.getCantBloques() <= cantidadElementosBuffer)
	{
		return getTamanioTablaBuffer();
	}
	//Cuento la cantidad de posiciones que hay en archivo y veo si hay nuevas en buffer.
	else
	{
		int cantPosiciones;
		cantPosiciones = archivoHash.getCantBloques();
		if(posInicioBuffer > cantPosiciones)
			cantPosiciones += getTamanioTablaBuffer();

		return cantPosiciones;
	}
}

int HashTable::getHash(int clave){
	int tamanio = getTamanioTabla();
	if(tamanio > 0)
		return clave % tamanio;
	else
		return 0;
}

string HashTable::getPath()
{
	return nombre;
}

bool HashTable::estaPosicionEnBuffer(int posicionTabla)
{
	//Me fijo si la posicion de la tabla esta dentro del buffer.
	if((posicionTabla >= posInicioBuffer) && (posicionTabla < (posInicioBuffer + cantidadElementosBuffer)))
		return true;
	else
		return false;
}

void HashTable::setIdBucket(int posicionTabla,int idBucket)
{
	//Me fijo si la posicion de la tabla esta dentro del buffer.
	if(estaPosicionEnBuffer(posicionTabla))
	{
		bufferBloques[posicionTabla - posInicioBuffer] = idBucket;
	}
	//Sino actualizo en disco y armo buffer.
	else
	{
		escribirBuffer(false);

		armarBuffer(posicionTabla);

		bufferBloques[posicionTabla - posInicioBuffer] = idBucket;
	}
}

int HashTable::getIdBucket(int posicionTabla)
{
	//Me fijo si la posicion de la tabla esta dentro del buffer.
	if(estaPosicionEnBuffer(posicionTabla))
	{
		return bufferBloques[posicionTabla - posInicioBuffer];
	}
	//Sino armo el buffer y consulto.
	else
	{
		escribirBuffer(false);

		armarBuffer(posicionTabla);

		return bufferBloques[posicionTabla - posInicioBuffer];
	}
}

int HashTable::obtenerIdBucket(int clave)
{
	int posicionTabla = getHash(clave);
	return getIdBucket(posicionTabla);
}

void HashTable::duplicar(int pos, int idBloqueNuevo){

	int tamanioTabla = getTamanioTabla();
	int razonRegistros;
	escribirBuffer(false);
	//Duplicamos la tabla en archivo.
	if(tamanioTabla == cantidadElementosBuffer)
		razonRegistros = 0;
	else
		razonRegistros = tamanioTabla / cantidadElementosBuffer;


	if(razonRegistros == 0)
	{
		if(!estaPosicionEnBuffer(0))
		{
			armarBuffer(0);
		}
		int tamanioEnBuffer = getTamanioTablaBuffer();

		if(tamanioEnBuffer < cantidadElementosBuffer)
		{
			int j=tamanioEnBuffer;

			for(int i=0;i<tamanioEnBuffer;i++)
			{
				bufferBloques[j] = bufferBloques[i];
				j++;
			}
		}
		else
			escribirBuffer(true);
	}
	//Aca por que tengo el archivo mas grande que el buffer , entonces es multiplo de 2^n.
	else
	{
		for(int i=0;i<razonRegistros;i++)
		{
			armarBuffer(i*cantidadElementosBuffer);
			escribirBuffer(true);
		}
	}

	//Seteo el nuevo id de bloque en la pos solicitada
	setIdBucket(pos,idBloqueNuevo);
}

int HashTable::calcularPosicion(int pos, int tamanioDispersion, bool up){
	int td = tamanioDispersion / 2;
	int tamanioTabla = this->getTamanioTabla();

	int r;
	if(up)
		r = pos + td;
	else r= pos - td;
	if (r >= tamanioTabla)
		r = r- tamanioTabla;
	if (r < 0)
		r= r + tamanioTabla;
	return r;
}

int HashTable::liberarBloque(int tamanioDispersion,int pos){
	int pos1 = calcularPosicion(pos, tamanioDispersion, false);
	int pos2 = calcularPosicion(pos, tamanioDispersion, true);
	int idBucket = getIdBucket(pos1);

	if(idBucket == getIdBucket(pos2))
	{
		setIdBucket(pos,idBucket);

		if (isTablaEspejada())
			truncarTabla();
		return idBucket;
	}
	return -1;
}

bool HashTable::isTablaEspejada(){
	int mitad = getTamanioTabla()/2;
	int i;
	for (i= 0; i< mitad; i++){
		if(getIdBucket(i) != getIdBucket(i + mitad))
			break;
	}
	if (i*2==this->getTamanioTabla())
		return true;
	return false;
}

void HashTable::truncarTabla()
{
	BlockedFile * archivoTemporal;
	int tamanioTabla = getTamanioTabla();
	int razonRegistros;
	escribirBuffer(false);
	//Duplicamos la tabla en archivo.
	if(tamanioTabla == cantidadElementosBuffer)
		razonRegistros = 0;
	else
		razonRegistros = tamanioTabla / cantidadElementosBuffer;


	if(razonRegistros == 0)
	{
		if(!estaPosicionEnBuffer(0))
		{
			armarBuffer(0);
		}
		int tamanioEnBuffer = getTamanioTablaBuffer();


		int i=tamanioEnBuffer / 2;
		if( i > 0)
		{
			for(int j= i;j<tamanioEnBuffer;j++)
			{
				bufferBloques[j] = -1;
			}
		}
	}

	//Aca por que tengo el archivo mas grande que el buffer, entonces es multiplo de 2^n.
	else
	{
		//Genero archivo temporal que luego se renombra al actual.

		string pathTemp = "hashtable1.dat";

		archivoTemporal = new BlockedFile(pathTemp,CREAR,sizeof(int));


		razonRegistros = razonRegistros / 2 ;

		for(int i=0;i<razonRegistros;i++)
		{
			armarBuffer(i*cantidadElementosBuffer);
			escribirBuffer(false,archivoTemporal);
		}

		delete archivoTemporal;
		archivoHash.close();

		string nom(nombre+ARCH_TABLA_EXT);
		//Borro el archivo viejo
		remove(nom.c_str());
		//Renombro el archivo temporal como el actual.
		rename(pathTemp.c_str(),nom.c_str());

		archivoHash.init(nom, ABRIR, sizeof(int));
	}
}
